import { Inject, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  Processor,
  Process,
  InjectQueue,
  OnQueueActive,
  OnQueueCompleted,
  OnQueueFailed,
} from '@nestjs/bull';
import { Job, Queue } from 'bull';
import { HttpService } from '@nestjs/axios';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
// acme
import * as acme from 'acme-client';
import * as rfc8555 from 'acme-client/types/rfc8555';
import { CertificateService } from '../certificate.service';
import { getCertStatus } from 'easy-ocsp';
// 设置dns服务器
import * as dns from 'dns';
dns.setServers([
  '8.8.8.8',
  '8.8.4.4',
  '114.114.114.114',
  '114.114.114.115',
  '223.5.5.5',
  '223.6.6.6',
  '119.29.29.29',
  '180.76.76.76',
]);
import * as _ from 'lodash';
import * as dayjs from 'dayjs';
// utils
import {
  extractDomainWithPrefix,
  DnsProviderFactory,
  IDnsProvider,
  asleep,
  retryFuncWithDelay,
  readCertificateInfo,
  expandWildcardDomains,
} from '@app/utils';
// acme.module
import { AcmeService } from '@app/share/acme/acme.service';
import { AcmeClientLogger } from '@app/share/acme/loggers';
import {
  ACME_CACHE_PREFIX,
  CERTIFICATE_AUTH_MODE,
  CERTIFICATE_CACHE_PREFIX,
  CERTIFICATE_TYPE,
  DATE_FORMAT,
} from '@app/common';
// dns modules
import { DnsService } from '@app/modules/dns/dns.service';
// notification
import { NotificationService } from '@app/modules/notification/notification.service';
import { RedisService } from '@liaoliaots/nestjs-redis';
import { Redis } from 'ioredis';
/**
 * IChallengeWithKeyAuthorization
 */
interface IChallengeWithKeyAuthorization extends rfc8555.ChallengeAbstract {
  token: string;
  keyAuthorization?: string;
}

/**
 * 证书生成处理器
 * 1.下单
 * 2.dns或者文件验证
 * 3.acme验证
 * 4.下载证书
 */
@Processor('certificate')
export class CertificateProcessor {
  private readonly logger = new Logger(CertificateProcessor.name);
  /**
   * acme客户端
   */
  private acmeClient: Map<string, acme.Client> = new Map<string, acme.Client>();
  /**
   * acme 客户端配置
   * @private
   */
  private acmeClientOptions: Map<string, acme.ClientOptions> = new Map<
    string,
    acme.ClientOptions
  >();
  /**
   * factory
   */
  private dnsFactory: Map<string, IDnsProvider> = new Map<
    string,
    IDnsProvider
  >();

  /**
   * 操作的DNS数据记录
   * @private
   */
  private dnsFactoryRR: Map<string, any[]> = new Map<string, []>();
  /**
   * 关闭队列id
   * @private
   */
  private acmeCloseJobId: Map<string, number | string> = new Map<string, 0>();

  // 错误信息，用于写入记录
  private error: Map<string, any> = new Map<string, ''>();

  // 记录是否更新证书流程
  private update: Map<string, any> = new Map<string, ''>();

  // 记录触发类型
  private triggerType: Map<string, any> = new Map<string, ''>();

  // Redis Client
  private readonly redisClient: Redis;

  constructor(
    @Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,
    @InjectQueue('certificate')
    private certificateQueue: Queue,
    private readonly redisService: RedisService,
    private readonly notificationService: NotificationService,
    private readonly configService: ConfigService,
    private readonly httpService: HttpService,
    private readonly acmeService: AcmeService,
    private readonly dnsService: DnsService,
    private readonly certificateService: CertificateService,
    private readonly acmeClientLogger: AcmeClientLogger,
  ) {
    this.redisClient = this.redisService.getOrThrow();
  }

  @OnQueueActive()
  async handleQueueActive(job: Job) {
    this.logger.debug(`============       OnQueueActive      ============`);
    this.logger.debug(
      `active ${job.name}: ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`,
    );
  }

  @OnQueueCompleted()
  async handleQueueCompleted(job: Job) {
    this.logger.debug(
      `completed ${job.name}: ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`,
    );
    this.logger.debug(`============    OnQueueCompleted   ================`);
  }

  @OnQueueFailed()
  async handleQueueFailed(job: Job) {
    this.logger.debug('===========   handleQueueFailed  start  ===========');
    const attemptsMade = job.attemptsMade;
    const maxAttempts = job.opts.attempts;
    if (attemptsMade === maxAttempts) {
      const { certificate } = job.data;
      this.logger.log('This was the last attempt, and it failed.');
      await this.updateCertificateJobInError(job);
      // 删除自动关单
      await this.removeAcmeCloseJob(certificate);
      // 删除job
      await job.remove();
      // 清理client
      this.clearAcmeClient(certificate);
      await this.removeDnsFactory(certificate);
    }
    this.logger.debug('===========   handleQueueFailed  end  ===========');
  }

  /**
   * 获取证书 acme客户端
   * @param user
   * @param certificate
   * @private
   */
  private async getCertificateAcmeClient(user: any, certificate: any) {
    if (!this.acmeClient.has(certificate.name)) {
      let clientOptions = this.acmeClientOptions.get(certificate.name);
      // TODO 如果也没有，就去数据库查找对应的
      if (!clientOptions) {
        clientOptions =
          await this.certificateService.getCertificateAcmeAccount(certificate);
      }
      // 初始化acme client 第一次传入要带agency
      const { client, account } = await this.acmeService.createAcmeClient(
        user,
        clientOptions,
        certificate.certAgency,
      );
      // update account
      await this.certificateService.createCertificateAcmeAccount(
        certificate,
        account,
      );
      // 设置acme client
      this.acmeClient.set(certificate.name, client);
      this.acmeClientOptions.set(certificate.name, account);
      // 延迟 500ms
      await asleep(500);
    }
    return this.acmeClient.get(certificate.name);
  }

  /**
   * 清理客户端和配置
   * @param certificate
   * @private
   */
  private clearAcmeClient(certificate: any) {
    this.acmeClient.delete(certificate.name);
    this.acmeClientOptions.delete(certificate.name);
  }

  /**
   * 取消关闭队列
   * @param certificate
   * @param jobId
   * @private
   */
  private async removeAcmeCloseJob(certificate: any, jobId?: number | string) {
    const job = await this.certificateQueue.getJob(
      jobId || this.acmeCloseJobId.get(certificate.name),
    );
    if (job) {
      await job.remove();
    } else {
      this.logger.log('Job not found, operation ignored.');
    }
  }

  /**
   * 移记录
   * @param certificate
   * @private
   */
  private async removeDnsFactory(certificate: any) {
    // 如果是DNS 操作 则需要删除dns记录
    const factory = this.dnsFactory.get(certificate.name);
    if (factory) {
      // 取得解析设置内容
      const userRR = this.dnsFactoryRR.get(certificate.name);
      this.logger.debug('acme-completed need to delete dns record');
      this.logger.debug(userRR);
      await Promise.all(
        userRR.map(async (rr: any) => {
          try {
            const { recordId, domain, RR, type } = rr;
            await factory.deleteRecord(domain, { recordId, RR, type });
            this.logger.log(`delete dns ${JSON.stringify(rr)} record success`);
            // asleep 停止3s 后处理
            await asleep(3e3);
          } catch (err) {
            this.logger.debug('delete dns record err:' + err.message);
          }
        }),
      );
      // factory
      this.dnsFactory.delete(certificate.name);
    }
  }

  /**
   * 是否处理状态
   * @param challenge
   * @private
   */
  private inProcessingChallengeStatus(
    challenge: IChallengeWithKeyAuthorization,
  ) {
    // 处理状态 pending / processing
    return ['pending', 'processing'].includes(challenge.status);
  }

  /**
   * 写入HTTP Challenge 并进行校验
   * @param certificate
   * @param challenge
   * @param identifier
   * @private
   */
  private async saveHTTPChallengeAndNextValidation(
    certificate: any,
    challenge: IChallengeWithKeyAuthorization,
    identifier: rfc8555.Identifier,
  ) {
    // 非处理状态直接处理
    if (!this.inProcessingChallengeStatus(challenge))
      return challenge.status === 'valid';

    try {
      // 1.写入token到redis
      await this.cacheManager.set(
        ACME_CACHE_PREFIX + `challenge:${challenge.token}`,
        challenge.keyAuthorization,
        600 * 1e3, // 10 分钟校验时间
      );
      // 2.主动校验5次，间隔3s,连续3次通过
      const userAgent = this.configService.get<string>('acme.user_agent');
      // 主动验证5次超时
      return await retryFuncWithDelay<boolean>(
        async () => {
          // 验证资源 `http://${identifier.value}/.well-known/acme-challenge/${challenge.token}`
          return new Promise(async (resolve, reject) => {
            try {
              const { data: validationToken } = await this.httpService
                .get(
                  `http://${identifier.value}/.well-known/acme-challenge/${challenge.token}`,
                  {
                    headers: {
                      'User-Agent': userAgent,
                    },
                  },
                )
                .toPromise();
              this.logger.debug('validation => ' + validationToken);
              this.logger.debug(
                'keyAuthorization => ' + challenge.keyAuthorization,
              );
              // 比对校验
              if (validationToken === challenge.keyAuthorization) {
                return resolve(true);
              }
              return reject('token file resolve err: validation failed.');
            } catch (err) {
              return reject('failed resolve http: ' + err.message);
            }
          });
        },
        5,
        3e3,
        3,
      );
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error(
        'saveHTTPChallengeAndNextValidation err:' + err.message,
      );
      return challenge.status === 'valid';
    }
  }

  /**
   * 写入DNS Challenge 并进行校验
   * @param certificate
   * @param challenge
   * @param identifier
   * @param authMode
   * @param user
   * @private
   */
  private async saveDNSChallengeAndNextValidation(
    certificate: any,
    challenge: IChallengeWithKeyAuthorization,
    identifier: rfc8555.Identifier,
    authMode?: number,
    user?: any,
  ) {
    // 非处理状态直接处理
    if (!this.inProcessingChallengeStatus(challenge))
      return challenge.status === 'valid';

    // 域名处理
    let mainDomain = 'certeasy.cn';
    let RRType: 'TXT' | 'CNAME' = 'TXT';
    // 用户解析记录RR
    let userRR = `_acme-challenge.${user.userCode}`;

    try {
      // 1. TODO 如果是DNS授权模式，是使用ak/sk给用户域名增加一个TXT解析记录
      // 如果用户指定了或者手写了key_secret and dnsServerId 拿去dns关联数据
      // 用户授权dns ,是直接操作用户的DNS记录 TXT _acme-challenge.example.cn 校验keyAuthorization
      // 处理当前域名 domain and prefix
      const { domain, prefix } = extractDomainWithPrefix(
        identifier.value,
        true,
      );
      // 授权模式下处理userRR
      if (Number(authMode) === CERTIFICATE_AUTH_MODE.DNS_AUTH_MODE) {
        mainDomain = domain;
        RRType = 'TXT';
        // 主域名没有前缀，处理.拼接
        userRR = `_acme-challenge${prefix ? `.${prefix}` : ''}`;
      } else {
        // 非授权模式要校验原始的，如果查不到CNAME代理解析就停止
        // TODO 如果错误，会抛出异常
        await retryFuncWithDelay(
          async () => {
            return await new Promise((resolve, reject) => {
              // 比如  baidu.com -> _acme-challenge.baidu.com 是否指向  _acme-challenge.userCode.certeasy.cn
              const hostname = `_acme-challenge${prefix ? `.${prefix}` : ''}.${domain}`;
              this.logger.debug('resolveCname dns: ' + hostname);
              dns.resolveCname(hostname, (err: any, records: any[]) => {
                if (err) {
                  // 记录错误
                  this.error.set(certificate.name, err.message);
                  this.logger.error('dns resolve err:' + err.message);
                  return reject(
                    'dns resolve err: DNS Challenge validation failed.',
                  );
                }
                this.logger.debug(
                  'resolveTxt => ' + records.map((record: any) => record[0]),
                );
                const validation = records.some((value: string) =>
                  value.includes(`${userRR}`),
                );
                if (!validation) {
                  return reject(`dns resolve err: does not match [${userRR}].`);
                }
                return resolve(validation);
              });
            });
          },
          5,
          1e3,
        );
      }
      // 2. 取得初始化的factory
      const factory = this.dnsFactory.get(certificate.name);
      // TODO 这里都是处理TXT记录的，代理是设置自己域名Certeasy.cn 相关用户代理 _acme-challenge.xxxxxx.certeasy.cn
      // TODO 如果是授权模式，是直接更新用户域名的TXT记录
      // TODO 都不用查了，直接写入吧，因为多域名是要多个TXT解析记录校验的
      const { recordId } = await factory.setRecord(mainDomain, {
        RR: userRR,
        type: RRType,
        value: challenge.keyAuthorization,
      });
      // 记录操作数据
      const factoryRR = this.dnsFactoryRR.get(certificate.name);
      if (factoryRR) {
        // 追加记录
        factoryRR.push({
          recordId,
          domain: mainDomain,
          RR: userRR,
          type: RRType,
        });
      }
      // 这里域名DNS是多个内容
      this.dnsFactoryRR.set(certificate.name, factoryRR);
      // asleep 停止3s 继续
      await asleep(3e3);

      //3. dns 验证重试
      // 验证方法要每个5s重试一次，一共30次
      return await retryFuncWithDelay<boolean>(
        async () => {
          return await new Promise((resolve, reject) => {
            const hostname = `${userRR}.${mainDomain}`;
            this.logger.debug('resolveTxt dns: ' + hostname);
            dns.resolveTxt(hostname, (err: any, records: any[]) => {
              if (err) {
                // 记录错误
                this.error.set(certificate.name, err.message);
                this.logger.error('dns resolve err:' + err.message);
                return reject('dns resolve err: validation failed.');
              }
              this.logger.debug(
                'resolveTxt => ' + records.map((record: any) => record[0]),
              );
              const validation = records
                .map((record: any) => record[0])
                .some((value: string) => value === challenge.keyAuthorization);
              if (!validation) {
                return reject('dns resolve err: validation failed.');
              }
              return resolve(validation);
            });
          });
        },
        30,
        3e3,
        3,
      );
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error('saveDNSChallengeAndNextValidation err:' + err.message);
      return challenge.status === 'valid';
    }
  }

  /**
   * asyncExecuteChallenge
   * @param auth
   * @param user
   * @param certificate
   * @private
   */
  private async asyncExecuteChallenge(auth: any, user: any, certificate: any) {
    // 加载acmeClient
    const client = await this.getCertificateAcmeClient(user, certificate);
    // 认证模式
    const { authMode } = certificate;
    // 进来这里单个域名处理
    const { challenges, identifier } = auth;
    this.logger.debug(`identifier => ${JSON.stringify(identifier)}`);
    this.logger.debug(`challenges => ${JSON.stringify(challenges)}`);
    // 取得用户选定的验证类型
    let challenge: IChallengeWithKeyAuthorization;
    let validation = false;
    // TODO 取得challenges特征 KeyAuthorization
    const challengesWithKeyAuthorization: IChallengeWithKeyAuthorization[] =
      await Promise.all(
        challenges.map(async (chall: any) => {
          const keyAuthorization =
            await this.acmeService.getChallengeKeyAuthorization(client, chall);
          this.logger.debug(
            `keyAuthorization => ${JSON.stringify(keyAuthorization)}`,
          );
          return {
            ...chall,
            keyAuthorization,
          };
        }),
      );

    this.logger.debug('challengesWithKeyAuthorization');
    this.logger.debug(challengesWithKeyAuthorization);
    // TODO 前置校验没通过, 直接失败得了，但是按道理应该都过
    if (
      challengesWithKeyAuthorization.every(
        (chall) => chall.status === 'invalid',
      )
    ) {
      return {
        validation: false,
        status: 'invalid',
        error: `ACME challenge active pre validation failed: Invalid Status`,
      };
    } else {
      // TODO 如果还是待处理的，继续处理
      // 直接请求acme-challenge
      // 文件验证模式
      if (
        [
          CERTIFICATE_AUTH_MODE.HTTP_AUTH_MODE,
          CERTIFICATE_AUTH_MODE.HTTP_ALIAS_AUTH_MODE,
        ].includes(Number(authMode))
      ) {
        challenge = challengesWithKeyAuthorization.find(
          (chall) => chall.type == 'http-01',
        );
        // HTTP 写入和校验
        validation = await this.saveHTTPChallengeAndNextValidation(
          certificate,
          challenge,
          identifier,
        );
      } else {
        challenge = challengesWithKeyAuthorization.find(
          (chall) => chall.type == 'dns-01',
        );
        // 成功还是失败
        validation = await this.saveDNSChallengeAndNextValidation(
          certificate,
          challenge,
          identifier,
          authMode,
          user,
        );
      }

      // asleep 停止3s 后处理
      await asleep(10e3);
    }

    // 打印file或者dns校验结果
    this.logger.debug('challenge =>' + JSON.stringify(challenge));
    this.logger.debug('validation =>' + validation);
    // TODO 前置校验没通过, 直接失败得了，但是按道理应该都过
    if (validation !== true) {
      return {
        validation: false,
        status: challenge.status,
        challenge,
        error: `ACME challenge active pre validation failed: ${this.error.get(certificate.name)}`,
      };
    }
    // 如果前置校验通过，返回challenge 给下一步检查
    return {
      validation: validation,
      status: challenge.status,
      challenge,
      error: '',
    };
  }

  /**
   * 提交远程校验
   * @param client
   * @param authorizationsResult
   * @private
   */
  private async asyncSubmitChallengeCompleted(
    client: acme.Client,
    authorizationsResult: any,
  ) {
    // TODO 进行远程校验, 同步校验结果返回
    return await Promise.all(
      authorizationsResult.map(async (result: any) => {
        // 前置校验通过，提交acme challenge
        let completeChallenge = {
          status: 'pending',
          challenge: result?.challenge || {},
          error: '',
        };
        // 处理状态 pending / processing
        const CHALL_IN_STATUS = ['pending', 'processing'];
        completeChallenge = await retryFuncWithDelay(
          async () => {
            return new Promise(async (resolve) => {
              // 提交acme challenge completed
              try {
                // 已经校验过的不再提交
                if (CHALL_IN_STATUS.includes(result?.challenge?.status)) {
                  await this.acmeService.submitChallengeCompleted(
                    client,
                    result?.challenge,
                  );
                }
                const { status, challenge } =
                  await this.acmeService.waitForChallengeValidStatus(
                    client,
                    result?.challenge,
                  );
                if (
                  CHALL_IN_STATUS.includes(result?.challenge?.status) ||
                  status === 'invalid'
                ) {
                  return resolve({
                    status,
                    error: `acme challenge completed err: validation ${status}.`,
                    challenge,
                  });
                }
                return resolve({ status, error: null, challenge });
              } catch (err) {
                return resolve({
                  status: 'invalid',
                  error: 'acme challenge completed err: ' + err.message,
                  challenge: result?.challenge,
                });
              }
            });
          },
          3,
          2e3,
        );
        return {
          ...result,
          ...completeChallenge,
        };
      }),
    );
  }

  /**
   * 吊销证书
   * @param job
   */
  @Process('acme-revoke')
  async handleRevoke(job: Job) {
    const { user, certificate, certificateDetail } = job.data;
    this.logger.debug(
      'revokeCertificate => ' + JSON.stringify(certificateDetail),
    );
    // 吊销证书
    try {
      const client = await this.getCertificateAcmeClient(user, certificate);
      // 吊销
      await this.acmeService.revokeCertificate(
        client,
        certificateDetail.certificate,
        {
          reason: 4, // 被取代使用reason 4
        },
      );
    } catch (err) {
      this.logger.error('revokeCertificate', err.message);
    }

    // 使用 ocsp 检查证书是否吊销
    const { status, revocationTime } = await getCertStatus(
      certificateDetail.certificate,
    );
    this.logger.debug(`getCertStatus => ${status} ${revocationTime}`);
    // 更新版本数据
    // 查找最新可用的validId
    const latestValidVersion =
      await this.certificateService.getCertificateLatestVersion(certificate.id);
    this.logger.debug(
      'latestValidVersion =>' + JSON.stringify(latestValidVersion),
    );
    // 更新证书状态和激活id
    await this.certificateService.updateCertificateById(certificate.id, {
      latestVersionId: latestValidVersion.id,
      latestValidVersionId: latestValidVersion.id,
    });
    // 更新证书版本信息
    await this.certificateService.updateCertificateVersion(
      certificate.id,
      {
        revokedTime: revocationTime
          ? dayjs(revocationTime).format('YYYY-MM-DD HH:mm:ss')
          : null,
        status:
          status.toUpperCase() === 'REVOKED'
            ? 4
            : status.toUpperCase() === 'UNKNOWN'
              ? 3
              : 2,
      },
      certificateDetail.certificateVersionId,
    );
  }

  @Process('acme-order')
  async handleOrder(job: Job) {
    const { user, certificate, update, triggerType } = job.data;
    // 记录是否更新流程
    this.update.set(certificate.name, update);
    // 记录触发类型
    this.triggerType.set(certificate.name, triggerType);
    // 自动关闭任务和下发challenge任务
    let autoCloseJob: Job<any>, autoChallengeJob: Job<any>;
    try {
      // 写入一个延时 30分钟，进行关闭处理
      const timeout = this.configService.get<number>(
        'acme.process_timeout',
        15,
      );
      autoCloseJob = await this.certificateQueue.add(
        'acme-close',
        { certificate },
        {
          delay: timeout * 60e3,
          attempts: 3,
          removeOnComplete: true,
          removeOnFail: true,
        },
      );
      this.logger.log(
        'add acme-close:' +
          dayjs().add(timeout, 'minute').format('YYYY-MM-DD HH:mm:ss'),
      );
      // 记录取消id, 用于后面取消队列
      this.acmeCloseJobId.set(certificate.name, autoCloseJob.id);
      this.logger.log(
        'acmeCloseJobId >> ' + this.acmeCloseJobId.get(certificate.name),
      );
      // 记录日志
      acme.setLogger((message: string) =>
        this.acmeClientLogger.log(
          message,
          `process_${certificate.id}_${certificate.latestVersionId}`,
        ),
      );
      // 拉取配置
      const client = await this.getCertificateAcmeClient(user, certificate);

      // 域名信息要做处理
      let domains = certificate.domains;
      // 泛域名补充
      if (certificate?.type === CERTIFICATE_TYPE.WILDCARD) {
        const { domain } = extractDomainWithPrefix(
          _.first(certificate.domains),
          true,
        );
        this.logger.log('wildcard domain:' + domain);
        // 1.单域名如果是没有www的，需要补充 www
        // 3.泛域名都补充 子主域名 *.example.com, example.com
        domains = expandWildcardDomains(certificate.domains, domain);
      }
      const identifiers = domains.map((ident: string) => {
        return {
          type: 'dns',
          value: ident,
        };
      });
      // 无数据直接忽略
      if (identifiers.length <= 0) return job.moveToCompleted();
      this.logger.log('add identifiers:' + JSON.stringify(identifiers));
      const order = await this.acmeService.placeOrder(client, {
        identifiers,
      });
      // 2.dns验证 - 进到这里来，说明用户已经在前台校验过了，需要平台配置challenge
      this.logger.log(
        'add acme-challenge:' + dayjs().format('YYYY-MM-DD HH:mm:ss'),
      );
      autoChallengeJob = await this.certificateQueue.add(
        'acme-challenge',
        { user, certificate, order, identifiers },
        {
          delay: 3e3,
          attempts: 3,
          removeOnComplete: true,
          removeOnFail: true,
        },
      );
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error('acme-order err:' + err.message);
      // 错误需要 移除acme-challenge
      if (autoChallengeJob) {
        await autoChallengeJob.remove();
      }
      // 抛出异常才会有下一次attempts
      throw new Error(err.message);
    }
  }

  /**
   * 更新错误数据
   * @param job
   */
  async updateCertificateJobInError(job: Job) {
    // 取得内容数据
    const { user, certificate, identifiers } = job.data;
    try {
      this.logger.debug('certificate =>' + JSON.stringify(certificate));
      // 操作之前检查是不是已经正常状态 2 了
      const info = await this.certificateService.getCertificateInfoById(
        certificate.id,
      );
      this.logger.debug('info =>' + JSON.stringify(info));
      const jobError = this.error.get(certificate.name);
      this.logger.debug('error =>' + JSON.stringify(jobError));
      // 更新当前版本错误信息
      await this.certificateService.updateCertificateVersion(
        certificate.id,
        {
          error: jobError,
          status: 0,
        },
        certificate.latestVersionId,
        this.triggerType.get(certificate.name),
      );
      this.logger.debug(
        'updateCertificateVersion =>' +
          JSON.stringify({
            error: jobError,
            status: 0,
          }),
      );

      // 过期时间
      let expiredTime: Date | string = '--';
      // 存在证书信息，且证书状态正常
      if (info && info?.status >= 2) {
        // 找到最后有效版本，如果是还有效就不更新证书
        const versionInfo =
          await this.certificateService.getCertificateVersionInfoById(
            certificate.latestValidVersionId,
          );
        this.logger.debug('versionInfo => ' + JSON.stringify(versionInfo));
        // 计算两个日期之间的天数
        const diffDays = dayjs(versionInfo.expiredTime).diff(
          dayjs(),
          'day',
          true,
        );
        expiredTime = versionInfo?.expiredTime || '--';
        // 如果证书版本过期了，则同步证书状态为0
        if (
          versionInfo?.status >= 2 &&
          versionInfo?.expiredTime &&
          diffDays <= 0
        ) {
          this.logger.debug(
            'update certificate info: ' +
              JSON.stringify({
                status: 0,
              }),
          );
          // 更新证书数据
          await this.certificateService.updateCertificateById(certificate.id, {
            status: 0,
          });
        } else {
          this.logger.log(
            'No update operation is required, the certificate status is maintained',
          );
        }
      } else {
        // 第一次版本申请，更新证书数据 status - 0
        await this.certificateService.updateCertificateById(certificate.id, {
          status: 0,
        });

        this.logger.log(
          'No update is required because the information does not exist or has been completed',
        );
      }

      // TODO 发送更新失败！！！
      await this.notificationService.triggerNotification(user, 2, {
        certificates: [
          {
            name: certificate.name,
            alias: '',
            domains: (identifiers || []).map((ident: any) => ident.value) || [],
            status: 0,
            expiredTime,
            error: jobError || null,
          },
        ],
      });
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error('acme-close => ' + err.message);
    }
  }

  @Process('acme-close')
  async handleClose(job: Job) {
    const { certificate } = job.data;
    // 写入超时错误
    this.error.set(certificate.name, 'ACME process timeout.');
    // 自动关单独处理
    await this.updateCertificateJobInError(job);
    // 清理client
    this.clearAcmeClient(certificate);
    await this.removeDnsFactory(certificate);
  }

  @Process('acme-challenge')
  async handleChallenge(job: Job) {
    const { user, certificate, order, identifiers } = job.data;
    try {
      // 加载acmeClient
      const client = await this.getCertificateAcmeClient(user, certificate);
      const authorizations = await this.acmeService.getAuthorizations(
        client,
        order,
      );
      // 认证模式
      const { authMode } = certificate;
      this.logger.debug('authMode =>' + authMode);
      this.logger.debug('authorizations =>' + JSON.stringify(authorizations));
      // TODO 前置处理 dnsFactory
      let providerName = 'aliyun';
      // 这里是需要处理平台的dns
      let dnsKey = this.configService.get<string>('dns.key');
      let dnsSecret = this.configService.get<string>('dns.secret');
      if (
        [
          CERTIFICATE_AUTH_MODE.DNS_AUTH_MODE,
          CERTIFICATE_AUTH_MODE.DNS_ALIAS_AUTH_MODE,
        ].includes(Number(authMode))
      ) {
        // TODO 如果用户指定了或者手写了key_secret and dnsServerId 拿去dns关联数据
        // 用户授权dns ,是直接操作用户的DNS记录 TXT _acme-challenge.example.cn 校验keyAuthorization
        if (Number(authMode) === CERTIFICATE_AUTH_MODE.DNS_AUTH_MODE) {
          // DNS AUTH 需要加载用户的DNS serverId 内容
          const dnsInfo = await this.dnsService.getDnsInfo(
            certificate.dnsServerId,
          );
          const { configJson } = await this.dnsService.getDnsProviderInfo(
            dnsInfo.providerId,
          );
          // 解构取得值数据
          [dnsKey, dnsSecret] = Object.values(dnsInfo.accessJson);
          providerName = configJson?.name;
        }

        // DnsProviderFactory
        const factory = DnsProviderFactory.createProvider(
          providerName,
          dnsKey,
          dnsSecret,
        );
        // 设置dns factory
        this.dnsFactory.set(certificate.name, factory);
        // 初始化 RR
        this.dnsFactoryRR.set(certificate.name, []);
      }
      // acme验证类型 http-01 dns-01 tls-alpn-0
      // 验证模式 1 file 2 file-alias 3 dns 4 dns-alias
      // 系统定义类型 http-01 [1 2] dns-01 [3 4]
      // 提取对应类型的challenge, 循环处理
      let authorizationsResult = await authorizations.reduce(
        async (promiseChain, currentTask) => {
          const results = await promiseChain;
          // 处理数据 asyncExecuteChallenge
          const challengeResult = await this.asyncExecuteChallenge(
            currentTask,
            user,
            certificate,
          );
          return [...results, challengeResult];
        },
        Promise.resolve([]),
      );
      // 初始值为 resolved 的 Promise 和空数组;
      this.logger.debug(
        'authorizationsResult =>' + JSON.stringify(authorizationsResult),
      );
      // asleep 停止5s 后处理
      await asleep(5e3);
      // 主动校验是否通过,通过了则提交ACME Challenge
      const invalidExecuteChallengeResult = authorizationsResult.find(
        (result: any) => result.validation != true,
      );
      if (invalidExecuteChallengeResult) {
        throw new Error(
          invalidExecuteChallengeResult?.error ||
            'authorization validation failed.',
        );
      }
      // 只有都是 result.validation 时进行远程校验, 同步校验结果返回(100%返回结果，没有异常抛出)
      authorizationsResult = await this.asyncSubmitChallengeCompleted(
        client,
        authorizationsResult,
      );
      // 初始值为 resolved 的 Promise 和空数组;
      this.logger.debug(
        'asnyc authorizationsResult =>' + JSON.stringify(authorizationsResult),
      );
      // 主动校验通过再处理 challenge.status == 'valid'
      // 直接读取是否有错误吧
      const invalidResult = authorizationsResult.find(
        (result: any) => result.validation != true || result?.status != 'valid',
      );
      // 多次主动验证不通过的话,抛出异常
      if (invalidResult) {
        throw new Error(
          invalidResult?.error || 'authorization validation failed.',
        );
      } else {
        this.logger.log(
          'add acme-completed:' + dayjs().format('YYYY-MM-DD HH:mm:ss'),
        );
        //延迟队列 5e3 后校验处理
        const acmeCompletedJob = await this.certificateQueue.add(
          'acme-completed',
          { user, certificate, identifiers, order },
          {
            delay: 5e3,
            attempts: 3,
            removeOnComplete: true,
            removeOnFail: true,
          },
        );
        this.logger.debug(
          'acmeCompletedJob:' + JSON.stringify(acmeCompletedJob),
        );
      }
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error('acme-challenge error:' + err.message);
      // 抛出异常才会有下一次attempts
      throw new Error(err.message);
    }
  }

  @Process('acme-completed')
  async handleCompleted(job: Job) {
    const { user, certificate, identifiers, order } = job.data;
    try {
      // 加载acmeClient
      const client = await this.getCertificateAcmeClient(user, certificate);
      const acmeOrder = await this.acmeService.getOrder(client, order);
      const commonName = identifiers[0].value;
      /* Finalize order */
      const [key, csr] = await acme.crypto.createCsr({
        commonName,
        altNames: (identifiers || []).map((ident: any) => ident.value) || [],
      });
      this.logger.debug('acme-completed.key =>' + key.toString());
      this.logger.debug('acme-completed.csr =>' + csr.toString());
      // 验证
      const cert = await this.acmeService.finalizeOrderGetCertificate(
        client,
        acmeOrder,
        csr,
      );
      this.logger.debug('acme-completed.cert =>' + cert.toString());
      // 清理client
      this.clearAcmeClient(certificate);
      await this.removeAcmeCloseJob(certificate);
      await this.removeDnsFactory(certificate);
      // 证书存储，写入Redis缓存
      this.logger.log(
        'add acme-cache:' + dayjs().format('YYYY-MM-DD HH:mm:ss'),
      );
      const ctxKey = `certificates:${certificate.id}:${certificate.latestVersionId}`;
      [
        { name: 'csr.pem', content: csr.toString() },
        { name: 'key.pem', content: key.toString() },
        { name: 'cert.pem', content: cert.toString() },
      ].forEach((ctx) => {
        this.logger.debug('acme-cache.ctx =>' + ctxKey);
        this.redisClient.hset(
          ACME_CACHE_PREFIX + ctxKey,
          ctx.name,
          ctx.content,
        );
      });
      //TODO  更新证书和记录版本
      // 读取证书信息
      const certInfo = readCertificateInfo(cert.toString());
      // 更新证书状态和激活id
      await this.certificateService.updateCertificateById(certificate.id, {
        latestValidVersionId: certificate.latestVersionId,
        status: 2,
      });
      // 更新证书版本信息
      await this.certificateService.updateCertificateVersion(
        certificate.id,
        {
          error: '',
          expiredTime: dayjs(certInfo.valid_to).format(DATE_FORMAT),
          status: 2,
        },
        certificate.latestVersionId,
        this.triggerType.get(certificate.name),
      );
      // 更新证书详情
      await this.certificateService.createCertificateDetail(
        certificate.latestVersionId,
        {
          ...certInfo,
          validFrom: certInfo.valid_from,
          validTo: certInfo.valid_to,
          csr: csr.toString(),
          key: key.toString(),
          certificate: cert.toString(),
        },
      );
      // 是否更新流程  下发证书更新通知
      if (this.update.get(certificate.name)) {
        await this.notificationService.triggerNotification(user, 2, {
          certificates: [
            {
              name: certificate.name,
              alias: '',
              domains:
                (identifiers || []).map((ident: any) => ident.value) || [],
              status: 2,
              expiredTime: dayjs(certInfo.valid_to).format(DATE_FORMAT),
            },
          ],
        });
        // TODO 证书是否需要部署，如果自动部署则需要更新云资源
        if (certificate && certificate.autoPush) {
          this.logger.debug('TODO 证书需要自动更新到云资源');
          try {
            await this.certificateService.autoDeploy(user, certificate?.id);
          } catch (e) {
            this.logger.error('autoDeploy error:' + e);
          }
        }
        // 恢复通知和检查
        await this.deleteLastDaysKeys(certificate?.id);
        await this.deleteLastDaysKeys(certificate?.id, 'lastUpdatedDays');
      }
    } catch (err) {
      // 记录错误
      this.error.set(certificate.name, err.message);
      this.logger.error('acme-completed err:' + err.message);
      // 抛出异常才会有下一次attempts
      throw new Error(err.message);
    }
  }

  /**
   * 移除通知和更新检查的缓存
   * @param id
   * @param rKey
   * @private
   */
  private async deleteLastDaysKeys(id: number, rKey = 'lastNotifiedDays') {
    const redisKey = `${CERTIFICATE_CACHE_PREFIX}${id}:${rKey}`;
    // 设置 Redis 键值
    await this.cacheManager.del(redisKey);
  }
}
